/*
Portal 系统的核心容器和管理器提供者
主要功能：
  创建 PortalContext 提供管理器方法
  监听全局事件（addType/removeType）处理跨组件通信
  管理操作队列，处理管理器未就绪时的操作缓冲
  提供 mount/update/unmount 方法给消费者使用
*/
import React from 'react'
import {DeviceEventEmitter, EventSubscription, NativeEventEmitter, StyleSheet, View} from 'react-native'
import PortalManager from './PortalManager'

export type PortalHostProps = {
  children: React.ReactNode
}

export type Operation =
  | {type: 'mount'; key: number; children: React.ReactNode}
  | {type: 'update'; key: number; children: React.ReactNode}
  | {type: 'unmount'; key: number}

export type PortalMethods = {
  mount: (children: React.ReactNode) => number
  update: (key: number, children: React.ReactNode) => void
  unmount: (key: number) => void
}

export const PortalContext = React.createContext<PortalMethods>(null as any)
// events
const addType = 'ANT_DESIGN_MOBILE_RN_ADD_PORTAL'
const removeType = 'ANT_DESIGN_MOBILE_RN_REMOVE_PORTAL'
const TopViewEventEmitter = DeviceEventEmitter || new NativeEventEmitter()

class PortalGuard {
  private nextKey = 10000

  add = (e: React.ReactNode) => {
    const key = this.nextKey++
    TopViewEventEmitter.emit(addType, e, key)
    return key
  }

  remove = (key: number) => TopViewEventEmitter.emit(removeType, key)
}

export const portal = new PortalGuard()

export default class PortalHost extends React.Component<PortalHostProps> {
  _nextKey = 0

  _queue: Operation[] = []

  _manager?: PortalManager

  _addType: EventSubscription

  _removeType: EventSubscription

  componentDidMount() {
    const manager = this._manager
    const queue = this._queue

    this._addType = TopViewEventEmitter.addListener(addType, this._mount)
    this._removeType = TopViewEventEmitter.addListener(removeType, this._unmount)

    while (queue.length && manager) {
      const action = queue.pop()
      if (!action) {
        continue
      }
      switch (action.type) {
        case 'mount':
          manager.mount(action.key, action.children)
          break
        case 'update':
          manager.update(action.key, action.children)
          break
        case 'unmount':
          manager.unmount(action.key)
          break
        default:
      }
    }
  }

  componentWillUnmount() {
    this._addType.remove()
    this._removeType.remove()
  }

  _setManager = (manager?: any) => {
    this._manager = manager
  }

  _mount = (children: React.ReactNode, _key?: number) => {
    const key = _key || this._nextKey++
    if (this._manager) {
      this._manager.mount(key, children)
    } else {
      this._queue.push({type: 'mount', key, children})
    }

    return key
  }

  _update = (key: number, children: React.ReactNode) => {
    if (this._manager) {
      this._manager.update(key, children)
    } else {
      const op: Operation = {type: 'mount', key, children}
      const index = this._queue.findIndex(o => o.type === 'mount' || (o.type === 'update' && o.key === key))

      if (index > -1) {
        this._queue[index] = op
      } else {
        this._queue.push(op)
      }
    }
  }

  _unmount = (key: number) => {
    if (this._manager) {
      this._manager.unmount(key)
    } else {
      this._queue.push({type: 'unmount', key})
    }
  }

  render() {
    // eslint-disable-next-line react/jsx-no-constructed-context-values
    const value = {
      mount: this._mount,
      update: this._update,
      unmount: this._unmount,
    }
    return (
      <PortalContext.Provider value={value}>
        {/* Need collapsable=false here to clip the elevations, otherwise they appear above Portal components */}
        <View style={styles.container} collapsable={false}>
          {this.props.children}
        </View>
        <PortalManager ref={this._setManager} />
      </PortalContext.Provider>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
})
